/* SPDX-License-Identifier: BSD-2-Clause */
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <dirent.h>
#include <time.h>
#include <locale.h>
#include <langinfo.h>
#include <wchar.h>

struct lc_era_t {
  char locale[64];
  char *date_fmt;
  char *d_fmt;
  char *d_t_fmt;
  char *t_fmt;
  char *t_fmt_ampm;
  char *era;
  char *era_d_fmt;
  char *era_d_t_fmt;
  char *era_t_fmt;
  char *alt_digits;
} era[512];
int ecnt = 0;

char *
xfrm_utf (const wchar_t *ws, int slist)
{
  static char xfrm[4096];
  char *p = xfrm;
  int wconst = 0;

  while (*ws)
    {
      if (*ws < 0x80 && (!wconst || !wcschr (L"aAbBcCdDeEfF", *ws)))
	{
	  *p++ = *ws;
	  wconst = 0;
	}
      else
	{
	  p += sprintf (p, "\\x%04lx", *ws);
	  wconst = 1;
	}
      ++ws;
      if (!*ws && slist)
      	{
	  ++ws;
	  if (*ws)
	    p += sprintf (p, ";");
	}
    }
  *p = '\0';
  return xfrm;
}

char *
xfrm_slist (const char *slist)
{
  static wchar_t wxfrm[4096], *wp;
  char *xfrm, *p, *ret;

  wp = wxfrm;
  while (*slist)
    {
      size_t len = mbstowcs (wp, slist, wxfrm + 4096 - wp) + 1;
      slist += strlen (slist) + 1;
      wp += len;
    }
  *wp++ = L'\0';
  xfrm = xfrm_utf (wxfrm, 1);
  p = xfrm;
  while (*p)
    p += strlen (p) + 1;
  ++p;
  ret = (char *) malloc (p - xfrm);
  memcpy (ret, xfrm, p - xfrm);
  return ret;
}

void
read_locale_era (char *name)
{
  char *nl, *nlera, *altd;
  char locale[64];
  wchar_t nlbuf[256];

  strcpy (locale, name);
  nl = strchr (locale, '@');
  if (nl)
    stpcpy (stpcpy (nl, ".utf8"), strchr (name, '@'));
  else
    strcat (locale, ".utf8");
  printf ("%s\n", locale);
  setlocale (LC_ALL, locale);

  nlera = nl_langinfo (ERA);
  altd = nl_langinfo (ALT_DIGITS);

  if (!*nlera && !*altd)
    return;

  strcpy (era[ecnt].locale, name);
  nl = nl_langinfo (_DATE_FMT);
  mbstowcs (nlbuf, nl, 256);
  era[ecnt].date_fmt = strdup (xfrm_utf (nlbuf, 0));
  nl = nl_langinfo (D_FMT);
  mbstowcs (nlbuf, nl, 256);
  era[ecnt].d_fmt = strdup (xfrm_utf (nlbuf, 0));
  nl = nl_langinfo (D_T_FMT);
  mbstowcs (nlbuf, nl, 256);
  era[ecnt].d_t_fmt = strdup (xfrm_utf (nlbuf, 0));
  nl = nl_langinfo (T_FMT);
  mbstowcs (nlbuf, nl, 256);
  era[ecnt].t_fmt = strdup (xfrm_utf (nlbuf, 0));
  nl = nl_langinfo (T_FMT_AMPM);
  mbstowcs (nlbuf, nl, 256);
  era[ecnt].t_fmt_ampm = strdup (xfrm_utf (nlbuf, 0));

  era[ecnt].era = *nlera ? xfrm_slist (nlera) : "\0";
  era[ecnt].alt_digits = *altd ? xfrm_slist (altd) : "\0";

  nl = nl_langinfo (ERA_D_FMT);
  mbstowcs (nlbuf, nl, 256);
  era[ecnt].era_d_fmt = strdup (xfrm_utf (nlbuf, 0));
  nl = nl_langinfo (ERA_D_T_FMT);
  mbstowcs (nlbuf, nl, 256);
  era[ecnt].era_d_t_fmt = strdup (xfrm_utf (nlbuf, 0));
  nl = nl_langinfo (ERA_T_FMT);
  mbstowcs (nlbuf, nl, 256);
  era[ecnt].era_t_fmt = strdup (xfrm_utf (nlbuf, 0));
  /* Serbian locale rename weirdness */
  if (!strncmp (era[ecnt].locale, "sr_RS", 5))
    {
      /* Create additional equivalent entries for the old locale sr_SP. */
      ++ecnt;
      memcpy (&era[ecnt], &era[ecnt - 1], sizeof era[ecnt]);
      era[ecnt].locale[3] = 'S';
      era[ecnt].locale[4] = 'P';
      /* Create additional equivalent entry for sr_ME@latin missing in Linux. */
      if (!strcmp (era[ecnt].locale, "sr_SP@latin"))
	{
	  ++ecnt;
	  memcpy (&era[ecnt], &era[ecnt - 1], sizeof era[ecnt]);
	  era[ecnt].locale[3] = 'M';
	  era[ecnt].locale[4] = 'E';
	}
    }
  ++ecnt;
}

int
locale_cmp (const void *a, const void *b)
{
  struct lc_era_t *la = (struct lc_era_t *) a;
  struct lc_era_t *lb = (struct lc_era_t *) b;
  return strcmp (la->locale, lb->locale);
}

void
create_list ()
{
  FILE *fp = fopen ("lc_era.h", "w");
  FILE *pp = popen ("rpm -q glibc", "r");
  char vers[64];
  int i;
  struct tm *tm;
  time_t tim;
  char tstr[64];

  fgets (vers, 64, pp);
  pclose (pp);
  if (strchr (vers, '\n'))
    *strchr (vers, '\n') = '\0';
  tim = time (NULL);
  tm = gmtime (&tim);
  strftime (tstr, 64, "%F", tm);
  fprintf (fp,
"/* This struct of LC_TIME ERA data has been generated by fetching locale\n"
"   data from a Linux system using %s on %s. */\n"
"\n"
"struct lc_era_t\n"
"{\n"
"  const char    *locale;\n"
"  const wchar_t *date_fmt;\n"
"  const wchar_t *d_fmt;\n"
"  const wchar_t *d_t_fmt;\n"
"  const wchar_t *t_fmt;\n"
"  const wchar_t *t_fmt_ampm;\n"
"  const wchar_t *era;\n"
"  const wchar_t *era_d_fmt;\n"
"  const wchar_t *era_d_t_fmt;\n"
"  const wchar_t *era_t_fmt;\n"
"  const wchar_t *alt_digits;\n"
"};\n"
"\n"
"static struct lc_era_t lc_era[] =\n"
"{\n", vers, tstr);

  qsort (era, ecnt, sizeof (struct lc_era_t), locale_cmp);
  for (i = 0; i < ecnt; ++i)
    fprintf (fp, "  { \"%s\", L\"%s\", L\"%s\", L\"%s\", L\"%s\", L\"%s\", "
		     "L\"%s\", L\"%s\", L\"%s\", L\"%s\", L\"%s\" },\n",
		 era[i].locale, era[i].date_fmt,
		 era[i].d_fmt, era[i].d_t_fmt,
		 era[i].t_fmt, era[i].t_fmt_ampm,
		 era[i].era, era[i].era_d_fmt,
		 era[i].era_d_t_fmt, era[i].era_t_fmt,
		 era[i].alt_digits);
  fputs ("};\n", fp);
  fclose (fp);
}

int
main ()
{
  char name[32], *c;
  FILE *pp;

  pp = popen ("locale -a | grep -a '_' | grep -F -v .", "r");
  if (!pp)
    {
      perror ("popen failed");
      return 1;
    }
  while (fgets (name, 32, pp))
    {
      c = strchr (name, '\n');
      if (c)
	*c = '\0';
      read_locale_era (name);
    }
  pclose (pp);
  create_list ();
  return 0;
}
